package com.basker.pisces.orm.session;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import com.basker.pisces.cache.DefaultValueWrapper;
import com.basker.pisces.cache.ICache;
import com.basker.pisces.tx.TransactionHelper;

/**
 * 在delegate之上加上一个基于事务声明周期的Map集合，当从缓存获取数据的时候，优先从事务内的缓存获取，没有找到的话再从delegate中获取；另外，在插入缓存的时候，只会添加到事务内的缓存，不会添加到delegate。
 *
 * @author hangwen
 */
class TransactionScopeDataContractCache implements ICache<String, Object> {

    /**
     * 在事务上下文内，创建DataContract缓存的装饰器，用于确保事务隔离
     *
     * @param delegate
     * @return
     */
    static ICache<String, Object> create(ICache<String, Object> delegate) {
        Assert.notNull(delegate, "parameter 'delegate' is required");
        Assert.isTrue(TransactionHelper.inTransactionScope(), "current thread is not in transactionScope");

        return new TransactionScopeDataContractCache(delegate);
    }

    private LocalMap map;
    private ICache<String, Object> delegate;

    private TransactionScopeDataContractCache(ICache<String, Object> delegate) {
        this.delegate = delegate;
        this.map = this.loadMap();
    }

    @Override
    public void clear() {
        this.map.clear();

        Object resource = TransactionSynchronizationManager.getResource(this);
        if (resource != null) {
            TransactionSynchronizationManager.unbindResource(this);
        }
    }

    @Override
    public boolean contains(String key) {
        return this.map.containsKey(key) || this.delegate.contains(key);
    }

    @Override
    public boolean equals(Object other) {
        if (other == this) {
            return true;
        }

        if (other instanceof TransactionScopeDataContractCache) {
            return ((TransactionScopeDataContractCache) other).getName().equals(this.getName());
        }

        return false;
    }

    @Override
    public ValueWrapper<Object> get(String key) {
        Set<String> keys = new HashSet<>(1, 1);
        keys.add(key);

        Map<String, ValueWrapper<Object>> result = this.getAll(keys);
        if (result == null || result.size() == 0) {
            return new DefaultValueWrapper<Object>(null);
        }

        return result.get(key);
    }

    @Override
    public Map<String, ValueWrapper<Object>> getAll(Set<String> keys) {
        int size = keys.size();

        Map<String, ValueWrapper<Object>> result = new LinkedHashMap<>(size, 1);
        // 存放事务缓存的数据
        Map<String, Object> resultFromMap = new LinkedHashMap<>(size, 1);
        // 访问delegate的key
        Set<String> keys2Delegate = new LinkedHashSet<>(size, 1);
        // 脏数据
        Set<String> dirtyKeys = this.map.dirtyKeys;

        for (String key : keys) {
            Object val = this.map.get(key);
            if (val != null) {
                resultFromMap.put(key, val);
            } else if (!dirtyKeys.contains(key)) {
                // 脏数据不可访问delegate，在事务声明周期内，脏数据要么从数据库获取，要么从事务缓存获取
                keys2Delegate.add(key);
            }
        }

        Map<String, ValueWrapper<Object>> resultFromDelegate = this.delegate.getAll(keys2Delegate);

        for (String key : keys) {
            ValueWrapper<Object> valueWrapper = null;

            Object val = resultFromMap.get(key);

            if (val != null) {
                valueWrapper = new DefaultValueWrapper<Object>(val);
            } else {
                // 事务缓存不存在
                if (!dirtyKeys.contains(key)) {
                    // 非脏数据，从二级缓存读取
                    valueWrapper = resultFromDelegate.get(key);
                    val = valueWrapper.get();
                    if (val != null) {
                        // 查询结果存起来，避免其他线程清除key后，造成不可重复读
                        this.map.put(key, val);
                    }

                } else {
                    // 脏数据，不从二级缓存读取，返回null，最后会通过数据库查询
                    valueWrapper = new DefaultValueWrapper<Object>(null);
                }

            }

            result.put(key, valueWrapper);
        }

        return result;
    }

    @Override
    public String getName() {
        return this.delegate.getName();
    }

    @Override
    public int hashCode() {
        return this.getName().hashCode();
    }

    @Override
    public void put(String key, Object value) {
        this.map.put(key, value);
    }

    @Override
    public void putAll(Map<String, Object> valueMap) {
        if (CollectionUtils.isEmpty(valueMap)) {
            return;
        }

        this.map.putAll(valueMap);
    }

    @Override
    public void remove(String key) {
        HashSet<String> keys = new HashSet<>(1, 1);
        keys.add(key);

        this.removeAll(keys);
    }

    @Override
    public void removeAll(Set<String> keys) {
        // 事务内缓存清除，并标记为脏数据
        for (String key : keys) {
            map.remove(key);
        }

        // 如果事务正常提交，再通知delegate删除缓存
        TransactionHelper.doBeforeCommit((readOnly) -> {
            delegate.removeAll(keys);
        });
    }

    private LocalMap loadMap() {
        Object resource = TransactionSynchronizationManager.getResource(this);
        if (resource instanceof LocalMap) {
            return (LocalMap) resource;
        }

        LocalMap map = new LocalMap();
        TransactionSynchronizationManager.bindResource(this, map);

        /**
         * 事务结束后（不管是提交还是回滚），都要清除cache
         */
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
            @Override
            public void afterCompletion(int status) {
                clear();
            }
        });

        return map;
    }

    /**
     * 事务缓存容器，从中删除key的时候会记录该key为脏数据
     *
     * @author hangwen
     */
    private class LocalMap extends HashMap<String, Object> {

        private static final long serialVersionUID = 8219773533634361696L;

        // 存放事务内脏数据的key,这些数据在事务内存在DML行为
        private Set<String> dirtyKeys = new HashSet<>();

        @Override
        public Object remove(Object key) {
            Object obj = super.remove(key);

            if (obj != null) {
                dirtyKeys.add((String) key);
            }

            return obj;
        }
    }

}
